/**
 * 可选地图供应商类型 (B:百度地图、A:高德地图、T:腾讯地图)
 */

const MAP_CONGFIG = {
  DEFAULT_MAP: 'T', // 默认使用的地图
  B: {
    MAP_NAME: '百度',
    MAP_KEY: '使用百度地图api时，此处可配置您自己的百度ak', // 百度密匙申请地址：https://lbsyun.baidu.com/apiconsole/key#/home
    MAP_BASE_URL: 'https://api.map.baidu.com', // api基础地址
    IP_LOCATION_API: '/location/ip', // IP定位接口地址
    COOR_TRANSLATE_API: '/geoconv/v1/', // 坐标转换接口地址
    INVERSE_GEOCODE_API: '/reverse_geocoding/v3/' // 逆地址解析接口地址
  },
  A: {
    MAP_NAME: '高德',
    MAP_KEY: '使用高德地图api时，此处可配置您自己的高德key', // 高德地图密匙申请地址：https://console.amap.com/dev/key/app
    MAP_BASE_URL: 'https://restapi.amap.com',
    IP_LOCATION_API: '/v3/ip',
    COOR_TRANSLATE_API: '/v3/assistant/coordinate/convert',
    INVERSE_GEOCODE_API: '/v3/geocode/regeo'
  },
  T: {
    MAP_NAME: '腾讯',
    MAP_KEY: '使用腾讯地图api时，此处可配置您自己的腾讯key', // 腾讯地图密匙申请地址：https://lbs.qq.com/dev/console/application/mine
    MAP_BASE_URL: 'https://apis.map.qq.com',
    IP_LOCATION_API: '/ws/location/v1/ip',
    COOR_TRANSLATE_API: '/ws/coord/v1/translate',
    INVERSE_GEOCODE_API: '/ws/geocoder/v1/'
  }
}

/**
 * @abstract 用于解决GET类型请求跨域的jsonp插件
 * @param url 请求接口地址
 * @param query 请求入参
 * @author xiao ma ge
 */
function jsonp(url, query = {}) {
  return new Promise((resolve, reject) => {
    // 根据时间戳生 + 随机数成一个callback回调名
    const callbackName = `jsonp_${new Date().getTime()}` + `${Math.random().toString().replace(/\D/g, '')}`
    // 创建一个script
    const script = document.createElement('script')
    // 字符串拼接生成基本url
    let baseUrl = `${url}${url.indexOf('?') === -1 ? '?' : '&'}callback=${callbackName}`
    // 遍历query对象拼接参数到url后
    for (const item in query) {
      const index = baseUrl.indexOf('?')
      baseUrl += `${index === -1 ? '?' : '&'}${item}=${query[item]}`
    }
    // jsonp核心，通过script的跨域特性发出请求
    script.src = baseUrl
    // 给window添加属性，用于获取jsonp结果
    window[callbackName] = (res) => {
      if (res) {
        resolve(res)
      } else {
        reject('未查询到任何数据')
      }
      // 删除window下属性
      delete window[callbackName]
      // 得到结果后删除创建的script
      document.body.removeChild(script)
    }
    // 动态创建script标记，错误的监听
    script.addEventListener('error', () => {
      delete window[callbackName]
      document.body.removeChild(script)
      reject('请求失败！')
    })
    // 把创建的script挂载到DOM
    document.body.appendChild(script)
  })
}

/**
 * @description 通过ip获取位置信息
 * @param params.type 通过某个地图供应工商提供的api服务获取ip位置信息： B 百度地图、A 高德地图、T 腾讯地图(默认)
 * @param params.key 对应地图的密匙；
 * @param params.ip IP地址，不传ip会默认根据发来请求的IP进行定位
 * @abstract 入参不传ip会默认根据发来请求的IP进行定位，
 * @abstract 测试发现腾讯地图提供的ip定位api最好用，基本都能定位到区，高德偏差最大，测试时我在成都被定位到了雅安
 * @abstract 注意，若获取失败，检查对应地图的Referer白名单是否配置，在地图密匙管理处配置
 * @link 百度地图ip定位文档: https://lbs.baidu.com/index.php?title=webapi/ip-api
 * @link 高德地图ip定位文档: https://lbs.amap.com/api/webservice/guide/api/ipconfig
 * @link 腾讯地图ip定位文档: https://lbs.qq.com/service/webService/webServiceGuide/webServiceIp
 */
const getLocationByIp = (
  params = {
    type: MAP_CONGFIG.DEFAULT_MAP
  }
) => {
  return new Promise(async (resolve, reject) => {
    const { type, key, ip } = params
    const url = `${MAP_CONGFIG[type].MAP_BASE_URL}` + `${MAP_CONGFIG[type].IP_LOCATION_API}`
    const _key = key ? key : `${MAP_CONGFIG[type].MAP_KEY}`
    if (!_key) {
      reject(`请传入${MAP_CONGFIG[type].MAP_NAME}地图的key`)
      return
    }
    const query = {
      key: _key,
      ak: _key,
      output: 'jsonp'
    }

    if (ip) {
      query.ip = ip
    }

    try {
      const res = await jsonp(url, query)
      resolve(res)
    } catch (error) {
      reject(error)
    }
  })
}

/**
 * @description 获取当前位置经纬度
 * @abstract 注意：必须在https的网站下才能使用，vue项目可配置vue.config.js文件devServer里面加https: true实现本地启动https的地址进行测试
 * @param params.type：B 百度坐标、A 高德坐标、T 腾讯坐标、 GPS坐标(默认)；
 * @param params.key：对应地图密匙，type参数为B/A/T时传对应地图密匙
 * @param params.enableHighAccuracy：是否开启高精度定位（默认true）
 * @param params.timeout：GPS定位失败超时时间
 * @param params.maximumAge：最长有效期,在重复获取地理位置时,此参数指定多久再次获取位置
 */
const getCurrentLngLat = (
  params = {
    type: 'GPS',
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 5000
  }
) => {
  const { type, key, enableHighAccuracy, timeout, maximumAge } = params
  return new Promise((resolve, reject) => {
    const returnData = {
      code: 200,
      message: '',
      data: {
        type,
        longitude: '',
        latitude: ''
      }
    }

    // 定位成功
    async function geoSuccess(event) {
      const longitude = event.coords.longitude
      const latitude = event.coords.latitude
      // gps坐标转其它坐标
      if (type !== 'GPS') {
        const res = await translateCoordinate(longitude, latitude, type, key)
        if (res.code === 200) {
          returnData.data.longitude = res.data.longitude
          returnData.data.latitude = res.data.latitude
          resolve(returnData)
          return
        }
        returnData.code = 500
        returnData.message = res.message
        resolve(returnData)
        return
      }
      returnData.data.longitude = longitude
      returnData.data.latitude = latitude
      returnData.message = '定位成功'
      resolve(returnData)
      return
    }

    // 定位失败
    function showError(error) {
      switch (error.code) {
        case error.PERMISSION_DENIED:
          returnData.code = 501
          returnData.message = '定位失败，请检查定位是否开启' // （或因非https的站点被浏览器安全策略阻止）
          break
        case error.POSITION_UNAVAILABLE:
          returnData.code = 502
          returnData.message = '定位失败，位置信息不可用'
          break
        case error.TIMEOUT:
          returnData.code = 503
          returnData.message = '定位失败，请求获取用户位置超时'
          break
        case error.UNKNOWN_ERROR:
          returnData.code = 504
          returnData.message = '定位失败，定位系统失效'
          break
        default:
          returnData.code = 505
          returnData.message = '定位失败，未知异常'
      }
      resolve(returnData)
    }

    // 获取用户的地理位置，使用它需要得到用户的授权
    function agreeObtainLocation() {
      const option = {
        enableHighAccuracy, // 启用高精度
        timeout, // 超时时间
        maximumAge // 最长有效期,在重复获取地理位置时,此参数指定多久再次获取位置
      }
      navigator.geolocation.getCurrentPosition(geoSuccess, showError, option)
    }

    // 判断浏览器是否支持地理位置接口
    if (navigator.geolocation) {
      // 支持
      agreeObtainLocation()
    } else {
      // 不支持,定位失败
      returnData.code = 5000
      returnData.message = '定位失败，当前浏览器不支持GPS定位'
      reject(returnData)
    }
  })
}

/**
 * @description GPS坐标转百度、高德、腾讯坐标
 * @param longitude GPS经度
 * @param latitude GPS纬度；
 * @param type  转换后的坐标类型 (B 百度地图、A 高德地图、T 腾讯地图；)
 * @param latitude key：对应地图的密匙
 */
async function translateCoordinate(longitude, latitude, type, key) {
  return new Promise(async (resolve, reject) => {
    const resolveData = {
      code: 200,
      data: {
        longitude: '',
        latitude: ''
      },
      message: ''
    }
    const _key = key ? key : `${MAP_CONGFIG[type].MAP_KEY}`
    const options = {
      locations: `${longitude},${latitude}`,
      type: 1,
      key: _key
    }
    let res = {
      code: 500,
      data: [],
      message: ''
    }
    if (type === 'B') {
      res = await otherCoordTransfromToBaidu(options)
    }
    if (type === 'A') {
      res = await otherCoordTransfromToGaode(options)
    }
    // GPS转腾讯地图坐标
    if (type === 'T') {
      res = await otherCoordTransfromToTencenter(options)
    }
    if (res.code === 200) {
      resolveData.data.longitude = res.data[0].longitude
      resolveData.data.latitude = res.data[0].latitude
      resolveData.message = '定位成功'
      resolve(resolveData)
      return
    }
    resolveData.code = 500
    resolveData.message = res.message
    reject(resolveData)
  })
}

/**
 * @description 其它坐标转百度地图坐标
 * @param options.locations 需转换的源坐标，多组坐标以“；”分隔（经度，纬度） 示例：29.575429778924,114.21892734521
 * @param options.type: 源坐标类型：1：GPS; 2：搜狗; 3：火星坐标; 4：墨卡托平面坐标; 5：百度地图采用的经纬度坐标（bd09ll）; 6：百度地图采用的墨卡托平面坐标（bd09mc）; 7：图吧地图坐标； 8：51地图坐标；
 * @param options.key: 百度地图ak密匙
 */
const otherCoordTransfromToBaidu = (options) => {
  return new Promise(async (resolve, reject) => {
    const { locations, type, key } = options
    const url = `${MAP_CONGFIG['B'].MAP_BASE_URL}` + `${MAP_CONGFIG['B'].COOR_TRANSLATE_API}`
    const _key = key ? key : `${MAP_CONGFIG['B'].MAP_KEY}`
    if (!_key) {
      reject(`请传入${MAP_CONGFIG['B'].MAP_NAME}地图的key`)
      return
    }
    const query = {
      coords: locations,
      from: type,
      ak: _key
    }
    try {
      const res = await jsonp(url, query)
      if (res.status === 0) {
        resolve({
          code: 200,
          data: res.result.map((item) => {
            return {
              longitude: item.x,
              latitude: item.y
            }
          }),
          message: '转换成功'
        })
        return
      }
      reject({
        code: 500,
        data: [],
        message: res.message || '转换失败'
      })
    } catch (error) {
      reject(error)
    }
  })
}

/**
 * @description 其它坐标转高德地图坐标
 * @param options.locations 经度和纬度用","分割，经度在前，纬度在后，经纬度小数点后不得超过6位。多个坐标对之间用”|”进行分隔最多支持40对坐标。
 * @param options.type 源坐标类型：1：GPS 2：mapbar 3：baidu 4：autonavi;
 * @param options.key 高德地图key密匙
 */
const otherCoordTransfromToGaode = (options) => {
  return new Promise(async (resolve, reject) => {
    const type_map = {
      1: 'gps',
      2: 'mapbar',
      3: 'baidu',
      4: 'autonavi'
    }
    const { locations, type, key } = options
    const url = `${MAP_CONGFIG['A'].MAP_BASE_URL}` + `${MAP_CONGFIG['A'].COOR_TRANSLATE_API}`
    const _key = key ? key : `${MAP_CONGFIG['A'].MAP_KEY}`
    if (!_key) {
      reject(`请传入${MAP_CONGFIG['A'].MAP_NAME}地图的key`)
      return
    }
    const query = {
      locations,
      coordsys: type_map[type],
      key: _key
    }
    try {
      const res = await jsonp(url, query)
      if (res.status === '1') {
        const resetLocations = res.locations.split(';')
        const newLocations = []
        resetLocations.forEach((item) => {
          const itemCoords = item.split(',')
          newLocations.push({
            longitude: itemCoords[0],
            latitude: itemCoords[1]
          })
        })
        resolve({
          code: 200,
          data: newLocations,
          message: '转换成功'
        })
        return
      }
      reject({
        code: 500,
        data: [],
        message: res.message || '转换失败'
      })
    } catch (error) {
      reject(error)
    }
  })
}

/**
 * @description 其它坐标转腾讯地图坐标
 * @param options.locations: 经度和纬度用","分割，经度在前，纬度在后，多组的话每组坐标之间使用”;"分隔；最多支持40对坐标。
 * @param options.type: 源坐标类型：1、GPS坐标 2、sogou经纬度 3、baidu经纬度 4、mapbar经纬度 5、[默认]腾讯、google、高德坐标 6、sogou墨卡托
 * @param options.key: 腾讯地图key密匙
 */
const otherCoordTransfromToTencenter = (options) => {
  return new Promise(async (resolve, reject) => {
    const { type, key } = options
    let { locations } = options
    const locationsArr = locations.split(';')
    const newLocations = locationsArr.map((item) => {
      const arrItem = item.split(',')
      return `${arrItem[1]},${arrItem[0]}`
    })
    locations = newLocations.join(';')
    const url = `${MAP_CONGFIG['T'].MAP_BASE_URL}` + `${MAP_CONGFIG['T'].COOR_TRANSLATE_API}`
    const _key = key ? key : `${MAP_CONGFIG['T'].MAP_KEY}`
    if (!_key) {
      reject(`请传入${MAP_CONGFIG['T'].MAP_NAME}地图的key`)
      return
    }
    const query = {
      locations,
      type,
      key: _key,
      output: 'jsonp'
    }
    try {
      const res = await jsonp(url, query)
      if (res.status === 0) {
        resolve({
          code: 200,
          data: res.locations.map((item) => {
            return {
              longitude: item.lng,
              latitude: item.lat
            }
          }),
          message: '转换成功'
        })
        return
      }
      reject({
        code: 500,
        data: [],
        message: res.message || '转换失败'
      })
    } catch (error) {
      reject(error)
    }
  })
}

/**
 * @description 逆地址编码（根据经纬度获取位置信息）
 * @param params.longitude 坐标经度
 * @param params.latitude 坐标纬度
 * @param params.type 传入的坐标类型： 'B' | 'A' | 'T' | 'GPS'
 * @param params.key 对应地图key密匙
 */
const inverseGeocoding = (params) => {
  return new Promise(async (resolve, reject) => {
    const { longitude, latitude, type, key } = params
    const newType = type === 'GPS' ? 'B' : type
    const url = `${MAP_CONGFIG[newType].MAP_BASE_URL}` + `${MAP_CONGFIG[newType].INVERSE_GEOCODE_API}`
    const _key = key ? key : `${MAP_CONGFIG[newType].MAP_KEY}`

    const callbackData = {
      code: 200,
      data: {
        addressComponent: null,
        formattedAddress: null,
        business: null,
        location: null
      },
      message: '解析地址成功'
    }
    if (newType === 'B') {
      try {
        const query = {
          location: `${latitude},${longitude}`,
          coordtype: type === 'GPS' ? 'wgs84ll' : 'bd09ll',
          ak: _key,
          output: 'json'
        }
        const res = await jsonp(url, query)
        if (res.status === 0) {
          callbackData.data.addressComponent = res.result.addressComponent
          callbackData.data.formattedAddress = res.result.formatted_address
          callbackData.data.business = res.result.business
          callbackData.data.location = res.result.location
          resolve(callbackData)
          return
        }
        callbackData.code = 500
        callbackData.message = '解析地址失败'
        resolve(callbackData)
      } catch (error) {
        reject(error)
      }
      return
    }
    if (newType === 'A') {
      try {
        const query = {
          location: `${longitude},${latitude}`,
          key: _key,
          output: 'json'
        }
        const res = await jsonp(url, query)
        if (res.status === '1') {
          callbackData.data.addressComponent = res.regeocode.addressComponent
          callbackData.data.formattedAddress = res.regeocode.formatted_address
          callbackData.data.business = res.regeocode.addressComponent.businessAreas[0].name
          callbackData.data.location = res.regeocode.addressComponent.businessAreas[0].location
          resolve(callbackData)
          return
        }
        callbackData.code = 500
        callbackData.message = '解析地址失败'
        resolve(callbackData)
      } catch (error) {
        reject(error)
      }
      return
    }
    if (newType === 'T') {
      try {
        const query = {
          location: `${latitude},${longitude}`,
          key: _key,
          output: 'jsonp'
        }
        const res = await jsonp(url, query)
        if (res.status === 0) {
          callbackData.data.addressComponent = res.result.address_component
          callbackData.data.formattedAddress =
            res.result.formatted_addresses.recommend || res.result.formatted_addresses.rough
          callbackData.data.location = res.result.ad_info.location
          resolve(callbackData)
          return
        }
        callbackData.code = 500
        callbackData.message = '解析地址失败'
        reject(callbackData)
      } catch (error) {
        reject(error)
      }
      return
    }
  })
}

export {
  getLocationByIp,
  getCurrentLngLat,
  translateCoordinate,
  otherCoordTransfromToBaidu,
  otherCoordTransfromToGaode,
  otherCoordTransfromToTencenter,
  inverseGeocoding
}
